package twoD.IsoPfem;

import inf.jlinalg.Vector2D;
import inf.jlinalg.Vector3D;
import inf.v3d.obj.Arrow;
import inf.v3d.obj.Cone;
import inf.v3d.obj.PolygonSet;
import inf.v3d.obj.Text;
import inf.v3d.obj.UnstructuredMesh;
import inf.v3d.view.Viewer;

import java.util.ArrayList;

public class Visualizer {

	private final int MAXIMUM_LOAD_PER_EDGE = 10;

	private double displacementScale_;
	private double constraintSymbolScale_;
	private double forceSymbolRadius_;
	private double forceSymbolScale_;
	private double textHeight_;

	private IStructure struct_;
	private Viewer view_;

	public Visualizer(Viewer view) {
		view_ = view;
	}

	public Visualizer(IStructure struct, Viewer view) {
		this(view);
		struct_ = struct;
	}

	public Viewer getView() {
		return view_;
	}

	public void drawElements() {
		UnstructuredMesh um = new UnstructuredMesh();
		for (int i = 0; i < struct_.getMesh().getNumberOfNodes(); i++) {
			Vector2D n = struct_.getMesh().getNodes(i).getPosition();
			um.addPoint(n.getX1(), n.getX2(), 0.0, 0.0);
		}
		for (int i = 0; i < this.struct_.getNumberOfElements(); i++) {
			int id0 = struct_.getElements(i).getNodes(0).getId();
			int id1 = struct_.getElements(i).getNodes(1).getId();
			int id2 = struct_.getElements(i).getNodes(2).getId();
			um.addCell(id0, id1, id2);
		}
		um.setBanded(true);
		// um.setColoringByData(true);
		// um.setContourLinesVisible(true);
		um.setOutlinesVisible(true);
		um.createColors();

		view_.addObject3D(um);
	}

	public void drawConstraints() {
		for (int i = 0; i < struct_.getMesh().getNumberOfNodes(); i++) {
			if (struct_.getMesh().getNodes(i).getConstraint() != null) {
				if (!struct_.getMesh().getNodes(i).getConstraint().isFree(0)) {
					Cone c = new Cone();
					c.setRadius(this.constraintSymbolScale_);
					c.setHeight(this.constraintSymbolScale_);
					c.setColor("blue");
					c.setDirection(1, 0, 0);
					c.setCenter(struct_.getMesh().getNodes(i).getPosition()
							.getX1()
							- this.constraintSymbolScale_, struct_.getMesh()
							.getNodes(i).getPosition().getX2(), 0.0);
					this.view_.addObject3D(c);
				}
				if (!struct_.getMesh().getNodes(i).getConstraint().isFree(1)) {
					Cone c = new Cone();
					c.setRadius(this.constraintSymbolScale_);
					c.setHeight(this.constraintSymbolScale_);
					c.setColor("blue");
					c.setDirection(0, 1, 0);
					c.setCenter(struct_.getMesh().getNodes(i).getPosition()
							.getX1(), struct_.getMesh().getNodes(i)
							.getPosition().getX2()
							- this.constraintSymbolScale_, 0.0);
					this.view_.addObject3D(c);
				}
			}
		}
	}

	public void drawForces() {
		for (int i = 0; i < struct_.getMesh().getNumberOfNodes(); i++) {
			Arrow a = new Arrow();

			double[] start = struct_
					.getMesh()
					.getNodes(i)
					.getPosition()
					.subtract(
							struct_.getMesh().getNodes(i).getForceVector()
									.multiply(this.forceSymbolScale_))
					.toArray();
			double[] end = struct_.getMesh().getNodes(i).getPosition()
					.toArray();

			a.setPoint1(start[0], start[1], 0.0);
			a.setPoint2(end[0], end[1], 0.0);
			a.setColor("red");
			a.setRadius(this.forceSymbolRadius_);

			this.view_.addObject3D(a);
		}
	}

	public void drawEdgeLoads() {
		for (int i = 0; i < this.struct_.getNumberOfElements(); i++) {
			for (EdgeLoad load : this.struct_.getElements(i).getEdgeLoadList()) {
				Node n1 = load.getN1();
				Node n2 = load.getN2();
				Vector2D dn = n2.getPosition().subtract(n1.getPosition());
				double l = dn.norm2();
				double c = dn.getX1() / l;
				double s = dn.getX2() / l;

				int n = MAXIMUM_LOAD_PER_EDGE;
				for (int j = 0; j < n + 1; j++) {
					Arrow a = new Arrow();

					Vector2D end = n1.getPosition().add(
							new Vector2D(l * c * j / n, l * s * j / n));
					double L = load.getLoad().getDomain().getLower();
					double D = load.getLoad().getDomain().getUpper() - L;
					double f = load.getLoad().valueAt(L + D * j / n)
							* this.forceSymbolScale_;
					Vector2D start = end.subtract(new Vector2D(-f * s, f * c));

					a.setPoint1(start.getX1(), start.getX2(), 0.0);
					a.setPoint2(end.getX1(), end.getX2(), 0.0);
					a.setColor("orange");
					a.setRadius(this.forceSymbolRadius_);

					this.view_.addObject3D(a);
				}
			}
		}
	}

	public void drawDisplacedStructure() {
		UnstructuredMesh um = new UnstructuredMesh();
		for (int i = 0; i < struct_.getMesh().getNumberOfNodes(); i++) {
			Vector2D n = struct_.getMesh().getNodes(i).getPosition();
			Vector2D dn = struct_.getMesh().getNodes(i).getDisplacement();
			um.addPoint(n.getX1() + displacementScale_ * dn.getX1(), n.getX2()
					+ displacementScale_ * dn.getX2(), 0.0, 0.0);
		}
		for (int i = 0; i < this.struct_.getNumberOfElements(); i++) {
			int id0 = struct_.getElements(i).getNodes(0).getId();
			int id1 = struct_.getElements(i).getNodes(1).getId();
			int id2 = struct_.getElements(i).getNodes(2).getId();
			um.addCell(id0, id1, id2);
		}
		um.setBanded(true);
		// um.setColoringByData(true);
		// um.setContourLinesVisible(true);
		um.setOutlinesVisible(true);
		um.createColors();

		view_.addObject3D(um);
	}

	public void drawSigma11() {
		PolygonSet ps = new PolygonSet();
		for (int i = 0; i < this.struct_.getNumberOfElements(); i++) {
			Node n0 = struct_.getElements(i).getNodes(0);
			Node n1 = struct_.getElements(i).getNodes(1);
			Node n2 = struct_.getElements(i).getNodes(2);
			// IVector s = this.struct_.getElement(i).computeStressVector();
			// ps.insertVertex(n0.getPosition().getX1(),
			// n0.getPosition().getX2(),
			// 0.0, s.get(0));
			// ps.insertVertex(n1.getPosition().getX1(),
			// n1.getPosition().getX2(),
			// 0.0, s.get(0));
			// ps.insertVertex(n2.getPosition().getX1(),
			// n2.getPosition().getX2(),
			// 0.0, s.get(0));
			double[] s1 = this.struct_.getElements(i).computeSigma1(1, 0, 0);
			double[] s2 = this.struct_.getElements(i).computeSigma1(0, 1, 0);
			double[] s3 = this.struct_.getElements(i).computeSigma1(0, 0, 1);
			ps.insertVertex(n0.getPosition().getX1(), n0.getPosition().getX2(),
					0.0, s1[0]);
			ps.insertVertex(n1.getPosition().getX1(), n1.getPosition().getX2(),
					0.0, s2[0]);
			ps.insertVertex(n2.getPosition().getX1(), n2.getPosition().getX2(),
					0.0, s3[0]);
			ps.polygonComplete();
		}
		ps.setVisible(true);
		ps.setColoringByData(true);
		ps.setOutlinesVisible(true);
		ps.setContourLinesVisible(true);
		ps.createColors();

		view_.addObject3D(ps);
	}

	public void setConstraintSymbolScale(double d) {
		this.constraintSymbolScale_ = d;
	}

	public void setForceSymbolRadius(double d) {
		this.forceSymbolRadius_ = d;
	}

	public void setDisplacementScale(double d) {
		this.displacementScale_ = d;
	}

	public void setForceSymbolScale(double d) {
		this.forceSymbolScale_ = d;
	}

	public void setTextHeightScale(double d) {
		this.textHeight_ = d;
	}

	public double automaticScale() {
		// setting the relative scale of the visual components
		// finding maximum distance between 2 points
		double d_max = 0;
		for (int i = 0; i < struct_.getMesh().getNumberOfNodes(); i++)
			for (int j = i + 1; j < struct_.getMesh().getNumberOfNodes(); j++) {
				double d = this.computeDistance(struct_.getMesh().getNodes(i),
						struct_.getMesh().getNodes(j));
				if (d_max < d)
					d_max = d;
			}

		// finding f_max
		double f_max = 0;
		for (int i = 0; i < struct_.getMesh().getNumberOfNodes(); i++) {
			double f = struct_.getMesh().getNodes(i).getForceVector().norm2();
			if (f_max < f)
				f_max = f;
		}
		for (int i = 0; i < this.struct_.getNumberOfElements(); i++)
			for (EdgeLoad load : this.struct_.getElements(i).getEdgeLoadList()) {
				/*
				 * temporary assignment; need to find absolute maximum value of
				 * edge load
				 */
				double f = Math.abs(load.getLoad().valueAt(0));
				if (f_max < f)
					f_max = f;
			}

		this.setForceSymbolScale(0.3 * d_max / f_max);
		this.setForceSymbolRadius(this.forceSymbolScale_ * f_max / 50);
		this.setConstraintSymbolScale(d_max / 60);

		this.setTextHeightScale(d_max / 50);

		// compute center of gravity of node (for text presentation later on)
		// this.centerOfNodeGravity_ = new Vector3D(0, 0, 0);
		// for (int i = 0; i < this.struct_.getNumberOfNodes(); i++)
		// this.centerOfNodeGravity_ = this.centerOfNodeGravity_
		// .add(this.struct_.getNode(i).getPosition());
		// this.centerOfNodeGravity_ = this.centerOfNodeGravity_.multiply(1 /
		// 3);

		return d_max;
	}

	public double postAutomaticScale(double d_max) {
		// finding u_max
		double u_max = 0;
		for (int i = 0; i < struct_.getMesh().getNumberOfNodes(); i++) {
			double u = struct_.getMesh().getNodes(i).getDisplacement().norm2();
			if (u_max < u)
				u_max = u;
		}
		double forceRelationRatio = 1 / forceSymbolScale_;
		this.setDisplacementScale(0.01 * d_max / u_max * forceRelationRatio);
		return u_max;
	}

	private double computeDistance(Node n1, Node n2) {
		return n2.getPosition().subtract(n1.getPosition()).norm2();
	}

	public void showNodeId() {
		for (int i = 0; i < struct_.getMesh().getNumberOfNodes(); i++) {
			Text t = new Text(Integer.toString(i));
			double[] pos = struct_.getMesh().getNodes(i).getPosition()
					.toArray();
			t.setOrigin(pos[0] + this.textHeight_, pos[1] + this.textHeight_,
					pos[2] + this.textHeight_);
			t.setHeight(this.textHeight_);
			t.setColor("magenta");
			// double rx = pos[0] - this.centerOfNodeGravity_.getX1();
			// double ry = pos[1] - this.centerOfNodeGravity_.getX2();
			// double rz = pos[2] - this.centerOfNodeGravity_.getX3();
			// t.setOrientation(rx, ry, rz);
			this.view_.addObject3D(t);
		}
	}

	public void showElementId() {
		for (int i = 0; i < struct_.getNumberOfElements(); i++) {
			Text t = new Text(Integer.toString(i));
			Vector3D pos = struct_.getElements(i).getCog();
			double d = struct_.getElements(i).getDiameter();
			double th = d / 15;
			t.setOrigin(pos.getX1(), pos.getX2() - th / 2, pos.getX3());
			t.setHeight(th);
			t.setColor("magenta");
			// double rx = pos[0] - this.centerOfNodeGravity_.getX1();
			// double ry = pos[1] - this.centerOfNodeGravity_.getX2();
			// double rz = pos[2] - this.centerOfNodeGravity_.getX3();
			// t.setOrientation(rx, ry, rz);
			this.view_.addObject3D(t);
		}

	}

	public void showElementGroupId(ArrayList<Integer> elementGroupId) {

	}

	public void drawElements(ArrayList<Integer> index, String color) {

	}

	public boolean advance() {
		/*
		 * use to advance step in dynamic visualizer return true if advance to
		 * last step
		 */
		return true;
	}

	public boolean retreat() {
		/*
		 * use to retreat step in dynamic visualizer return true if retreat to
		 * initial step
		 */
		return true;
	}

}
